package graphgenetics;

import java.util.Random;

public class GraphItem implements PopulationMember<GraphItem> {

    private double rating;
    TreeGenerator t;
    RandomArrayBuilder r;
    public boolean[] bitstring;
    private int len;

    /**
     * Creates a new graph with the array bits representing the connections
     * between the verticies.
     * @param bits The connections between verticies
     * @param len The number of vertecies
     */
    public GraphItem(boolean[] bits, int len, TreeGenerator tg, RandomArrayBuilder rab) {
        this.t = tg;
        this.r = rab;
        bitstring = bits;
        this.len = len;

        this.rating = calcRating();
    }

    /**
     * Creates a new graph with the String s representing a bitstring
     * which represents the connections between the verticies.
     * @param s The bitstring in '1's and '0's (characters)
     * @param len The number of vertecies
     */
    public GraphItem(String s, int len, TreeGenerator tg, RandomArrayBuilder rab) {
        this.t = tg;
        this.r = rab;
        int length = s.length();
        bitstring = new boolean[length];
        for (int i = 0; i < length; i++) {
            bitstring[i] = (s.charAt(i) == '1') ? true : false;
        }
        this.len = len;
        this.rating = calcRating();
    }

    /**
     * Creates a graph with random connections.
     * @param r The source of the randomness
     * @param len The number of vertecies
     * @param even True is there should be the same number of 1s as 0s
     */
    public GraphItem(Random r, int len, boolean even, TreeGenerator tg, RandomArrayBuilder rab) {
        this.t = tg;
        this.r = rab;
        this.len = len;
        bitstring = new boolean[len*(len-1)/2];

        if (even) {
            fillEven(r);
        } else {
            fill(r);
        }
        this.rating = calcRating();
    }

    /**
     * Fills the bit string with an even number of ones and zeros
     * @param r The source of the randomness
     */
    private void fillEven(Random r) {
        int ran, i = 0;
        int stringLen = bitstring.length;
        int a = stringLen/2;
        int b = stringLen - a;
        while (a+b > 0) {
            ran = r.nextInt(a+b);
            if (ran < a) {
                a--;
                bitstring[i] = true;
            } else {
                b--;
                bitstring[i] = false;
            }
            i++;
        }
    }

    /**
     * Fills the bit string with random bits
     * @param r The source of the random bits
     */
    private void fill(Random r) {
        int stringLen = bitstring.length;
        for (int i = 0; i < stringLen; i++) {
            bitstring[i] = r.nextBoolean();
        }
    }


    /**
     * Returns the state of the connection between vertex x and y.
     * @param x the first vertex
     * @param y the second vertex
     * @return whether they are connected
     */
    public boolean get(int x, int y) {
        // you can't go and see if a node connects to itself
        if ( x == y ) {
            throw new IllegalArgumentException("Error in Graph.get: " +
                    "Cannot compare a node with itself");
        }
        // swap if x if greater than y
        if (x > y) {
            int temp = y;
            y = x;
            x = temp;
        }
        return bitstring[y + x*len - (x+1)*(x+2)/2]; // magic
    }

    /**
     * Sets the color of a specific connection
     * @param x The first vertex
     * @param y The second vertex
     * @param bit the color
     */
    public void set(int x, int y, boolean bit) {

        if ( x == y ) {
            throw new IllegalArgumentException("Error in Graph.get: " +
                    "Cannot compare a node with itself");
        }
        // swap if x if greater than y
        if (x > y) {
            int temp = y;
            y = x;
            x = temp;
        }
        bitstring[y + x*len - (x+1)*(x+2)/2] = bit;
    }

    /**
     * Creates a string of '1' and '0' characters representing the contents
     * of the bit string
     * @return the bit string
     */
    @Override
    public String toString() {
        StringBuilder out = new StringBuilder();
        for (boolean b : bitstring) {
            out.append((b)?'1':'0');
        }
        return out.toString();
    }

    /**
     * Calculates the rating for the graph, to
     * be used by getRank(), which is used by
     * comapreTo().
     * @return the rating
     */
    private double calcRating() {
        // TODO: Make this smarter
        //return countCliques(); /*
        double val = 0;
        boolean[][] graph = asArray();
        for (int i = 0; i < 43; i++) {
            int[] order = r.makeArray(i);
            t.makeTree(graph, order);
            int treeSize = t.getMaxLine();
            if (treeSize >= 5) val += treeSize*treeSize;
        }
        return val;
        // */
    }

    /**
     * Returns the rank (but does not compute it)
     * used for sorting.
     * @return the sort rank
     */
    public double getRank() {
        return rating;
    }

    /**
     * Unimplimented. If it did anything it would
     * do the same as createNew, but store the results in this.
     * @param a The first graph item
     * @param b The second graph item
     */
    public void combine(GraphItem a, GraphItem b) {
        // TODO: make an in-place combine function?
    }

    /**
     * Creates a new Graph based on two previous graphs
     * @param a The first graph item
     * @param b The second graph item
     * @param r The source of randomness
     * @return A new graph item.
     */
    public static GraphItem createNew(GraphItem a, GraphItem b, Random r) {
        // Flip one bitstring if it has more differerces than similarities?
        int swapProbability = 3;
        int len = a.len, bitlen = a.bitstring.length;
        boolean[] bits = new boolean[bitlen];
        boolean s = r.nextBoolean();
        int swapAt = 1 + r.nextInt() % swapProbability;
        for ( int i = 0; i < bitlen; i++ ) {
            bits[i] = s ? a.bitstring[i] : b.bitstring[i];
            //if ( r.nextInt() % 7 == 0 ) s = !s;
            if ( i == swapAt ) {
                s = !s;
                swapAt = 1 + i + (r.nextInt() % swapProbability);
            }
        }
        return new GraphItem(bits, len, a.t, a.r);
    }

    public GraphItem mutate(Random r, int n) {
        boolean[] bits = (boolean[]) this.bitstring.clone();
        int size = bits.length;
        for (int i = 0; i < n; i++) {
            int index = r.nextInt(size);
            bits[index] = ! bits[index];
        }
        return new GraphItem(bits, this.len, this.t, this.r);
    }

    /**
     * Compares (for sort order) to another graph
     * @param other the other graph
     * @return the sort order of the two graphs
     */
    public int compareTo(GraphItem other) {
        double otherRank = other.getRank(), thisRank = this.getRank();
        if (otherRank > thisRank) {
            return 1;
        } else if (otherRank < thisRank) {
            return -1;
        }
        return 0;
    }

    /**
     * Returns the number of 5-cliques in the graph
     * @return the number of cliques
     */
    public int countCliques() {
        boolean[][] graph = asArray();
        int size = len, count = 0;
        boolean color;
        for (int a = 0; a < size; a++) {
            for (int b = a + 1; b < size; b++) {

                color = graph[a][b];

                for (int c = b+1; c<size; c++) {
                    if ( graph[a][c] != color
                            || graph[b][c] != color ) {
                        continue;
                    }

                    for (int d = c+1; d<size; d++) {

                        if ( graph[a][d] != color
                                || graph[b][d] != color
                                || graph[c][d] != color ) {
                            continue;
                        }

                        for (int e = d+1; e<size; e++) {

                            if ( graph[a][e] == color
                                    && graph[b][e] == color
                                    && graph[c][e] == color
                                    && graph[d][e] == color ) {
                                count++;
                            }
                        }
                    }
                }
            }
        }
        return count;
    }

    /**
     * "Draws" a matrix based on the connections of the graph.
     * @return the matrix stored in a string
     */
    public String toMatrix() {
        StringBuilder s = new StringBuilder();

        for ( int i = 0; i < len; i++ ) {
            s.append("[ ");
            for ( int j = 0; j < len; j++ ) {
                if ( i == j ) s.append("~ ");
                else if ( get(i, j) ) s.append("1 ");
                else s.append("0 ");
            }
            s.append("]\n");
        }

        return s.toString();
    }

    /**
     * Counts the number of 'zeros' (blue connections) in the graph.
     * @return the number of zeros
     */
    public int countZeros() {
        int x = 0;
        for (boolean b : bitstring) if (!b) x++;
        return x;
    }

    /**
     * Counts the number of 'ones' (red connections) in the graph.
     * @return the number of ones
     */
    public int countOnes() {
        int x = 0;
        for (boolean b : bitstring) if (b) x++;
        return x;
    }

    /**
     * Prints out info about the graph
     */
    public void printInfo() {
        System.out.println("Rank: " + getRank());
        System.out.println("5-Cliques: " + countCliques() );
        System.out.println("Blue connections: " + countZeros() );
        System.out.println("Red connections: " + countOnes() );
        System.out.println("Bitstring:");
        System.out.println(toString());
    }

    /**
     * Turns the bit string into an array for faster repeated indexing
     * @return the graph connection matrix
     */
    public boolean[][] asArray() {
        boolean[][] out = new boolean[len][len];
        int index = 0; // Position in bitstring
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                out[i][j] = bitstring[index];
                out[j][i] = bitstring[index];
                index++;
            }
        }
        return out;
    }



    /**
     * I am unsure exactly how to do it with the GraphItems so I did it with
     * boolean[]
     * Unimplemented
     * @param a The first graph item
     * @param b The second graph item
     * @param c The number of bits it takes from each GraphItem before switching
     * Graphs
     */
    public boolean[] combineSetNumber(boolean[] a, boolean[] b, int c)
    {
        boolean[] bits = new boolean[a.length];
        boolean ab = true;
        for(int i = 0; i < bits.length; i = i + c)
        {
            if(i + c > bits.length)
                c = bits.length - i;
            if(ab)
            {
                System.arraycopy(a, i, bits, i, i + c);
                ab = false;
            }
            else
            {
                System.arraycopy(b, i, bits, i, i + c);
                ab = true;
            }
        }
        return bits;
    }

}
